
export class Paging<T = {}, P = {}> {
  readonly page_size: number;
  readonly loader: (paging: Paging<T, P>) => Promise<T[]>;
  get finish() { return this._finish; }
  get items() { return this._items; }
  get page() { return this._page; }
  get loading() { return this._loading; }
  get error() { return this._error; }
  get total() { return this._total; }
  get params() { return this._params; };
  protected _jid: number = 0;
  protected _finish: boolean = false;
  protected _items: T[] = [];
  protected _page: number = 1;
  protected _loading: boolean = false;
  protected _error: any;
  protected _total: number = -1;
  protected _params: P
  protected _on_loading?: (arg0: this) => void
  protected _on_finish?: (arg0: this) => void
  on_loading(f: typeof this._on_loading) {
    this._on_loading = f;
    return this;
  }
  on_finish(f: typeof this._on_finish) {
    this._on_finish = f;
    return this;
  }
  constructor(page_size: number, p: P, loader: (paging: Paging<T, P>) => Promise<T[]>) {
    this.page_size = page_size;
    this._params = p;
    this.loader = loader;
  }
  set_total(total: number): this {
    this._total = total;
    return this;
  }
  private _already_loading_error() {
    return Promise.reject(Object.assign(new Error('already loading!'), { _already_loading_error: true }))
  }
  private _already_done_error() {
    return Promise.reject(Object.assign(new Error('already done!'), { _already_done_error: true }))
  }
  private _cancelled_error() {
    return Promise.reject(Object.assign(new Error('cancelled!'), { _cancelled_error: true }))
  }

  load(reset: boolean = false, params: P | ((prev: P | null) => P) = this._params): Promise<T[]> {
    if (this._loading)
      return this._already_loading_error()
    if (!reset && this._finish)
      return this._already_done_error()
    if (reset) {
      this._finish = false;
      this._page = 1;
    } else {
      this._page += 1;
    }
    this._params = typeof params === 'function' ? (params as any)(this._params) : params
    this._loading = true;
    this._on_loading?.(this);
    const jid = ++this._jid
    return this.loader(this).then((l) => {
      if (jid !== this._jid)
        return this._cancelled_error();
      this._items = reset ? [...l] : [...this._items, ...l];
      // 当总数未指定，且下一次加载返回空
      if (l.length <= 0 && this._total === 0) this._total = l.length;
      this._finish = (l.length < this.page_size) || (this._total >= 0 && this._items.length >= this._total);
      this._on_finish?.(this);
      this._loading = false;
      this._error = void 0;
      this._on_loading?.(this)
      return this._items;
    }).catch((e) => {
      if (jid !== this._jid)
        return this._cancelled_error();
      this._loading = false;
      this._error = e;
      if (this._page > 1) this._page -= 1;
      this._on_loading?.(this);
      return Promise.reject(e);
    });
  }

  /**
   * 捕获Paging产生的不严重错误
   * 例：
   *    - 加载中时，调用load；
   *    - 完成加载后仍load(false)
   * 
   * @static
   * @param {*} e 异常
   * @memberof Paging
   */
  static forgivingCatch(e: any): void;
  /**
   * 捕获Paging产生的不严重错误，并返回“回退值”
   *
   * @static
   * @template T
   * @param {*} e 异常
   * @param {T} fallback 回退值
   * @return {*}  {T}
   * @memberof Paging
   */
  static forgivingCatch<T = {}>(e: any, fallback: T): T;
  static forgivingCatch<T = {}>(e: any, fallback?: T): T | undefined {
    if (this.isForgivableError(e))
      return fallback;
    throw e;
  }
  static isForgivableError(e: any) {
    if (!e) return false;
    return (
      true === e._already_loading_error ||
      true === e._already_done_error ||
      true === e._cancelled_error
    )
  }
}
